/*
 ABOUT THE FIRMWARE
 colorduino_firmware_rigel - alternative firmware for colorduino boards produced by ITeadStudio
 Copyright (c) 2011 Vichaya Sidhipong, bsidhipong@gmail.com

 This file is part of colorduino_firmware_rigel.

 colorduino_firmware_rigel is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.
 
 colorduino_firmware_rigel is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with colorduino_firmware_rigel.  If not, see <http://www.gnu.org/licenses/

 VERSION HISTORY
 0.1     5/20/2011 initial release under GPLv3

 */

#if defined(X86DEBUG)
#include "x86debug.h"
#else
#include <Wire.h>
#include <avr/pgmspace.h>
#endif
#include "i2c_commands.h"
#include "colorduino.h"

#define elementsof(name,type) (sizeof(name)/sizeof(type))
rgbframe_t frame_buffers[] = {
	/* primary buffer */
	{
		{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},
		{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},
		{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},
		{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},
		{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},
		{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},
		{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},
		{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00},{0x00,0x00,0x00}
	},
	/* secondary buffer (rgb gradation) */
	{
		{0x00,0x00,0xFF},{0x66,0x00,0xFF},{0xEE,0x00,0xFF},{0xFF,0x00,0xDD},{0xFF,0x00,0x77},{0xFF,0x00,0x11},{0xFF,0x44,0x00},{0xFF,0xBB,0x00},
		{0x66,0x00,0xFF},{0xEE,0x00,0xFF},{0xFF,0x00,0xDD},{0xFF,0x00,0x77},{0xFF,0x00,0x11},{0xFF,0x44,0x00},{0xFF,0xBB,0x00},{0xFF,0xFF,0x00},
		{0xEE,0x00,0xFF},{0xFF,0x00,0xDD},{0xFF,0x00,0xFF},{0xFF,0x00,0x11},{0xFF,0x44,0x00},{0xFF,0xBB,0x00},{0xFF,0xFF,0x00},{0xAA,0xFF,0x00},
		{0xFF,0x00,0xDD},{0xFF,0x00,0xAA},{0xFF,0x00,0x11},{0xFF,0x44,0x00},{0xFF,0xBB,0x00},{0xFF,0xFF,0x00},{0xAA,0xFF,0x00},{0x33,0xFF,0x00},
		{0xFF,0x00,0x77},{0xFF,0x00,0x11},{0xFF,0x44,0x00},{0xFF,0xBB,0x00},{0xFF,0xFF,0x00},{0xAA,0xFF,0x00},{0x33,0xFF,0x00},{0x00,0xFF,0x11},
		{0xFF,0x00,0x11},{0xFF,0x44,0x00},{0xFF,0xBB,0x00},{0xAA,0xFF,0x00},{0xAA,0xFF,0x00},{0x33,0xFF,0x00},{0x00,0xFF,0x11},{0x00,0xFF,0x77},
		{0xFF,0x44,0x00},{0xFF,0xBB,0x00},{0xFF,0xFF,0x00},{0xAA,0xFF,0x00},{0x33,0xFF,0x00},{0x00,0xFF,0x11},{0x00,0xFF,0x77},{0x00,0xFF,0xEE},
		{0xFF,0xBB,0x00},{0xFF,0xFF,0x00},{0xAA,0xFF,0x00},{0x33,0xFF,0x00},{0x00,0xFF,0x11},{0x00,0xFF,0x77},{0x00,0xFF,0xEE},{0x00,0xDD,0xFF}
	},
	/* calibration */
	{
		{0xFF,0x00,0x00},{0xEE,0x00,0x00},{0xDD,0x00,0x00},{0xCC,0x00,0x00},{0x00,0xFF,0x00},{0x00,0xEE,0x00},{0x00,0xDD,0x00},{0x00,0xCC,0x00}, /* RRRRGGGG */
		{0xBB,0x00,0x00},{0xAA,0x00,0x00},{0x99,0x00,0x00},{0x88,0x00,0x00},{0x00,0xBB,0x00},{0x00,0xAA,0x00},{0x00,0x99,0x00},{0x00,0x88,0x00}, /* RRRRGGGG */
		{0x77,0x00,0x00},{0x66,0x00,0x00},{0x55,0x00,0x00},{0x44,0x00,0x00},{0x00,0x77,0x00},{0x00,0x66,0x00},{0x00,0x55,0x00},{0x00,0x44,0x00}, /* RRRRGGGG */
		{0x33,0x00,0x00},{0x22,0x00,0x00},{0x11,0x00,0x00},{0x00,0x00,0x00},{0x00,0x33,0x00},{0x00,0x22,0x00},{0x00,0x11,0x00},{0x00,0x00,0x00}, /* RRRRGGGG */
		{0x00,0x00,0xFF},{0x00,0x00,0xEE},{0x00,0x00,0xDD},{0x00,0x00,0xCC},{0xFF,0xFF,0xFF},{0xEE,0xEE,0xEE},{0xDD,0xDD,0xDD},{0xCC,0xCC,0xCC}, /* BBBBWWWW */
		{0x00,0x00,0xBB},{0x00,0x00,0xAA},{0x00,0x00,0x99},{0x00,0x00,0x88},{0xBB,0xBB,0xBB},{0xAA,0xAA,0xAA},{0x99,0x99,0x99},{0x88,0x88,0x88}, /* BBBBWWWW */
		{0x00,0x00,0x77},{0x00,0x00,0x66},{0x00,0x00,0x55},{0x00,0x00,0x44},{0x77,0x77,0x77},{0x66,0x66,0x66},{0x55,0x55,0x55},{0x44,0x44,0x44}, /* BBBBWWWW */
		{0x00,0x00,0x33},{0x00,0x00,0x22},{0x00,0x00,0x11},{0x00,0x00,0x00},{0x33,0x33,0x33},{0x22,0x22,0x22},{0x11,0x11,0x11},{0x00,0x00,0x00}  /* BBBBWWWW */
	}
};
#define COUNT_OF_FRAMEBUFFERS elementsof(frame_buffers,rgbframe_t)
volatile uint8_t scan_line = 0; /* scanline currently being refreshed (0..7) */

/* store command and parameter(s) received via i2c */
uint8_t i2c_buffer[96];

/* used by the low-level rendering functions */
uint8_t view = 2; /* buffer to hold currently-visible frame */
uint8_t draw = 1; /* drawing occurs in back buffer */
uint8_t refresh = 2; /* interrupt-service routine uses this index to avoid flickering if buffer-swapping occurs during matrix refresh */

/* current paper (background) color, defaults to black */
uint8_t paper_red = 0;
uint8_t paper_green = 0;
uint8_t paper_blue = 0;

/* current ink (foreground) color, defaults to green (RGB=00CC00, delicious shade of terminal green) if not explicitly set */
/* feel free to change to amber (RGB=FFCC00) if you prefer WYSE terminal or something */
uint8_t ink_red = 0;
uint8_t ink_green = 0xcc;
uint8_t ink_blue = 0;

volatile enum state_labels {
	wait_for_command,
	process_command,
	synchronize_with_master
} state = wait_for_command;

void loop ( void )
{
	void do_periodic_task( void );

	switch ( state ) {
	case wait_for_command:
		/* BIG TASKS:
		   Use the available CPU time to do background tasks, perhaps query RTC/accelerometer and update display accordingly.
		   This is also a good place to update the display if you intend to use the Colorduino as a clock, for example. */
		do_periodic_task( );
		break;

	case process_command:
		process_wire_command( );
		state = synchronize_with_master;
		break;

	case synchronize_with_master:
		/* SMALL TASKS:
		   While waiting for the master to synchronize, do really small tasks such as updating button input states, etc.
		   Try not to use i2c bus here as the master may be waiting to use it to obtain a succesful acknowledgement. */
		break;

	default:
		state = wait_for_command;
		break;
	}
}

void process_wire_command ( void )
{
}
 
void show_buffer ( void )
{
	uint8_t temp = draw;
	/* do not change 'refresh' here, as it will introduce flicker in the interrupt-driven matrix refresh code */
	draw = view;
	view = temp;
}

void switch_buffer ( uint8_t which )
{
	if ( which < COUNT_OF_FRAMEBUFFERS ) {
		view = which;
		while ( refresh != view ) delay( 10 );
	}
}

void draw_pixel_rgb ( uint8_t col, uint8_t row, uint8_t red, uint8_t green, uint8_t blue )
{
	register uint8_t *ptr = (uint8_t *) &frame_buffers[draw][(row * 8) + col];
	*ptr++ = blue; *ptr++ = green; *ptr++ = red;
}

void draw_pixel ( uint8_t col, uint8_t row )
{
	register uint8_t *ptr = (uint8_t *) &frame_buffers[draw][(row * 8) + col];
	*ptr++ = ink_blue; *ptr++ = ink_green; *ptr++ = ink_red;
}

/* Brasenham line drawing algorithm, see http://en.wikipedia.org/wiki/Bresenham's_line_algorithm  */
void draw_line ( uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1 )
{
	bool steep = abs(y1 - y0) > abs(x1 - x0);
	if ( steep ) { /* swap(x0,y0) and swap(x1,y1) */
		uint8_t temp;
		temp = x0; x0 = y0; y0 = temp;
		temp = x1; x1 = y1; y1 = temp;
	}
	if ( x0 > x1 ) { /* swap(x0,x1) and swap(y0,y1) */
		uint8_t temp;
		temp = x0; x0 = x1; x1 = temp;
		temp = y0; y0 = y1; y1 = temp;
	}
	int16_t delta_x = x1 - x0;
	int16_t delta_y = abs( y1 - y0 );
	int16_t error = delta_x / 2;
	int16_t ystep;
	int16_t y = y0;
	ystep = (y0 < y1) ? 1 : -1;
	for ( uint8_t x = x0; x <= x1; x++ ) {
		if (steep) draw_pixel( y, x );
		else draw_pixel( x, y );
		error = error - delta_y;
		if ( error < 0 ) {
			y = y + ystep;
			error = error + delta_x;
		}
	}
}

void fill_buffer_rgb ( uint8_t red, uint8_t green, uint8_t blue )
{
	register uint8_t *ptr = (uint8_t *) &frame_buffers[draw];
	*ptr++ = blue; *ptr++ = green; *ptr++ = red;
	memcpy( ptr, &frame_buffers[draw], sizeof(rgbframe_t)-3 );
}

void clear_buffer ( void )
{
	fill_buffer_rgb( 0, 0, 0 );
}

void handle_request_event ( void )
{
	/* send our current state when the master requests it */
	Wire.send( state );
	if ( state == synchronize_with_master ) state = wait_for_command; /* break the main event loop */
}

void handle_receive_event ( int how_many )
{
	uint8_t i = 0;
	while ( Wire.available() ) {
		i2c_buffer[i] = Wire.receive();
		if ( i < sizeof(i2c_buffer) ) i++;
	}
	state = process_command; 
}

/* hardware port definitions (named as found in colorduino schematic) */
#define LED_RST			PORTC
#define LED_RST_BIT		0x04
#define LED_LAT			PORTC
#define LED_LAT_BIT		0x02
#define LED_SELBNK		PORTC
#define LED_SELBNK_BIT	0x01
#define LED_SDA			PORTD
#define LED_SDA_BIT		0x80
#define LED_SCL			PORTD
#define LED_SCL_BIT		0x40
#if defined(X86DEBUG)
#define RAISE(signal)	{printf(#signal" high\n");}
#define LOWER(signal)	{printf(#signal" low\n");}
#else
#define RAISE(signal)	{signal|=signal##_BIT;}
#define LOWER(signal)	{signal&=~signal##_BIT;}
#endif

/* colorduino schematic, ATMEGA8P <-> M54564FP */
#define close_lines		{PORTD=0x00;PORTB=0x00;}
#define open_line_0		{PORTB=0x01;} /* C0 */
#define open_line_1		{PORTB=0x02;} /* C1 */
#define open_line_2		{PORTB=0x04;} /* C2 */
#define open_line_3		{PORTB=0x08;} /* C3 */
#define open_line_4		{PORTB=0x10;} /* C4 */
#define open_line_5		{PORTB=0x20;} /* C5 */
#define open_line_6		{PORTD=0x08;} /* C6 */
#define open_line_7		{PORTD=0x10;} /* C7 */

/* DM163: gray-channel correction terms (6 bits, 0..63) */
uint8_t gamma_red = 23;
uint8_t gamma_green = 63;
uint8_t gamma_blue = 63;

inline void shiftout ( register uint8_t data, register uint8_t bits )
{
	while ( bits ) {
		if ( data & 0x80 ) RAISE( LED_SDA )
		else LOWER( LED_SDA );
		data = (data << 1);
		LOWER( LED_SCL );
		RAISE( LED_SCL );
		bits--;
	}
}

void setup ( void )
{
	cli( );
	/* configure ports, see http://www.arduino.cc/en/Reference/PortManipulation */
	/* ATmega48A/48PA/88A/88PA/168A/168PA/328/328PA datasheet (doc8271.pdf) referred to as "ATmega datasheet" */
	DDRB = DDRC = DDRD = 0xFF;
	PORTB = PORTC = PORTD = 0;
	memset( i2c_buffer, 0, sizeof(i2c_buffer) );
	Wire.begin( I2C_DEVICE_ADDRESS );
	Wire.onReceive( handle_receive_event );
	Wire.onRequest( handle_request_event );
	/* reset the colorduino */
	RAISE( LED_RST );
	LOWER( LED_RST );
	RAISE( LED_RST );
	/* set gray-level correction terms for the matrix */
	LOWER( LED_LAT );
	LOWER( LED_SELBNK );
	for ( uint8_t columns = 0; columns < 8; columns++ ) {
		shiftout( gamma_blue << 2, 6 );
		shiftout( gamma_green << 2, 6 );
		shiftout( gamma_red << 2, 6 );
	}
	RAISE( LED_SELBNK );
	/* back to the ATmega */
	/* init timer2, used to control PWM flashing of the LED matrix */
	/* datasheet: ATmega, Table 17-8 Waveform Generation Mode Bit Description */
	/* WGM [2:0], 011 = Fast PWM */
	/* WGM [2:0], 000 = Normal */
	TCCR2A &= ~((1 << WGM21) | (1 << WGM20));
	TCCR2B &= ~WGM22;
	/* datasheet: ATmega, Table 17-9 Clock Select Bit Description
	   +------+------+------+--------------------+
	   | CS22 | CS21 | CS20 | Description        |
	   |------+------+------+--------------------|
	   |  0   |  0   |  0   | No clock source    |
	   |  0   |  0   |  1   | No prescaling      |
	   |  0   |  1   |  0   | /8                 |
	   |  0   |  1   |  1   | /32                |
	   |  1   |  0   |  0   | /64                |
	   |  1   |  0   |  1   | /128               |
	   |  1   |  1   |  0   | /256               |
	   |  1   |  1   |  1   | /1024              |
	   +------+------+------+--------------------+ */
	TCCR2B |= ((1 << CS22) | (1 << CS20));
	TCCR2B &= ~(1 << CS21);
	/* internal clock source */
	ASSR |= (1 << AS2);
	/* enable Timer2 Counter2 Overflow Interrupt, disable Compare Match A, and Compare Match B interrupts */
	TIMSK2 |=  (1 << TOIE2);
	TIMSK2 &= ~(1 << OCIE2B);
	TCNT2 = 64;
	sei( );
}

ISR ( TIMER2_OVF_vect )
{
	cli( );
	close_lines;
	/* DM163: select bank 1, prepare to shift in image data */
	RAISE( LED_SELBNK );
	LOWER( LED_LAT );
	/* Shift out the graylevel data for blue, green, and red channels in that order */
	/* NOTE: Need to keep the column/row ordering compatible with Rainbowduino.  In the Rainbowduino, the origin (0,0) is
	 * on the side where the JST power plugs in, and the lower-right corner (7,7) is above where the 3-pin male header plugs
	 * into the next Rainbowduino in chain.  In the Colorduino, line_0 is at the bottom and line_7 is at the top (compared
	 * to the Rainbowduino, so it's basically upside-down and if someone were to somehow hook a Rainbowduino up to a Colorduino
	 * then one of them will be upside-down with respect to the other :-)
	 */
	uint8_t bytes = 8 * 3;
	register uint8_t *p = (uint8_t *) &frame_buffers[refresh][(scan_line) * 8];
	while ( bytes-- ) shiftout( *p++, 8 );
	RAISE( LED_LAT );
	LOWER( LED_LAT );
	/* Now that we've shifted out a complete row of pixels, we can switch to "refresh" buffer.
	   This might introduce some tearing if rendering occurs fast enough, in which case
	   we might want to switch to "refresh" buffer at the end of a complete frame. */
	refresh = view;
	/* M54564FP */
	close_lines;
	switch ( scan_line ) {
		case 0: open_line_7; break;
		case 1: open_line_6; break;
		case 2: open_line_5; break;
		case 3: open_line_4; break;
		case 4: open_line_3; break;
		case 5: open_line_2; break;
		case 6: open_line_1; break;
		case 7: open_line_0; break;
	}
	scan_line = (++scan_line % 8);
	TCNT2 = 64;
	sei( );
}

/* vim: set noexpandtab tabstop=4 shiftwidth=4 nolist autoindent cindent: */
